﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;

namespace devtm.Aop.Extensions
{


    public static class Empty<T>
    {
        public static readonly T[] Array = new T[0];
    }


    public static class TypeDefinitionExt
    {




        public static void GetTypes(this IEnumerable<TypeDefinition> typeDefinitions, IList<TypeDefinition> definitions)
        {
            foreach (var typeDefinition in typeDefinitions)
            {
                GetTypes(typeDefinition.NestedTypes, definitions);
                definitions.Add(typeDefinition);

            }
        }



        public static MethodReference GetAndImportMethod(this TypeDefinition td,
                    Func<MethodDefinition, bool> search, TypeReference target)
        {
            var md = td.Methods.First(search);
            return target.Module.Import(md);
        }

        public static MethodReference GetMethodByNameAndImport(this TypeDefinition td,
                    string name, TypeReference target)
        {
            return td.GetAndImportMethod(md => md.Name == name, target);
        }




        public static IEnumerable<string> GetPropertyNames(this TypeDefinition type)
        {
            do
            {
                foreach (var propertyDefinition in type.Properties)
                {
                    yield return propertyDefinition.Name;
                }

                if (type.BaseType == null || type.BaseType.FullName == "System.Object")
                {
                    yield break;
                }
                type = type.BaseType.Resolve();
            } while (true);
        }


        public static IEnumerable<PropertyDefinition> GetAllProperties(this TypeDefinition type)
        {
            do
            {
                foreach (var propertyDefinition in type.Properties)
                {
                    yield return propertyDefinition;
                }

                if (type.BaseType == null || type.BaseType.FullName == "System.Object")
                {
                    yield break;
                }
                type = type.BaseType.Resolve();
            } while (true);
        }


        public static IEnumerable<FieldDefinition> GetAllFields(this TypeDefinition type)
        {
            do
            {
                foreach (var fieldDefinition in type.Fields)
                {
                    yield return fieldDefinition;
                }

                if (type.BaseType == null || type.BaseType.FullName == "System.Object")
                {
                    yield break;
                }
                type = type.BaseType.Resolve();
            } while (true);
        }


        public static IEnumerable<MethodDefinition> GetConstructors(this TypeDefinition self)
        {
            if (self == null)
                throw new ArgumentNullException("self");

            if (!self.HasMethods)
                return Empty<MethodDefinition>.Array;

            return self.Methods.Where(method => method.IsConstructor);
        }

        public static MethodDefinition GetStaticConstructor(this TypeDefinition self)
        {
            if (self == null)
                throw new ArgumentNullException("self");

            if (!self.HasMethods)
                return null;

            return self.GetConstructors().FirstOrDefault(ctor => ctor.IsStatic);
        }

        public static IEnumerable<MethodDefinition> GetMethods(this TypeDefinition self)
        {
            if (self == null)
                throw new ArgumentNullException("self");

            if (!self.HasMethods)
                return Empty<MethodDefinition>.Array;

            return self.Methods.Where(method => !method.IsConstructor);
        }

        public static TypeReference GetEnumUnderlyingType(this TypeDefinition self)
        {
            if (self == null)
                throw new ArgumentNullException("self");
            if (!self.IsEnum)
                throw new ArgumentException();

            return Mixin.GetEnumUnderlyingType(self);
        }

    }


    public static partial class Mixin
    {

        public static TypeReference GetEnumUnderlyingType(this TypeDefinition self)
        {
            var fields = self.Fields;

            for (int i = 0; i < fields.Count; i++)
            {
                var field = fields[i];
                if (!field.IsStatic)
                    return field.FieldType;
            }

            throw new ArgumentException();
        }

        public static TypeDefinition GetNestedType(this TypeDefinition self, string name)
        {
            if (!self.HasNestedTypes)
                return null;

            var nested_types = self.NestedTypes;

            for (int i = 0; i < nested_types.Count; i++)
            {
                var nested_type = nested_types[i];
                if (nested_type.Name == name)
                    return nested_type;
            }

            return null;
        }


        /// <summary>
        /// Search in the type the same method signature.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="methodSign"></param>
        /// <returns></returns>
        public static MethodDefinition AsMethod(this TypeDefinition type, MethodDefinition methodSign)
        {

            foreach (var item in type.Methods)
            {

                if (item.Name == methodSign.Name)

                    if (item.Parameters.AreEquals(methodSign.Parameters) &&
                    item.GenericParameters.AreEquals(methodSign.GenericParameters))

                        return item;

            }

            // voir pour de la recursivité

            return null;

        }

    }
    
}
